﻿using System;
using System.Diagnostics;
using System.Globalization;
using System.Web;
using System.Web.Security;
using BLL.Security;

namespace Web.Infrastructure.Security
{
    using System.Net;

    /// <summary>
    /// Use this class as a wrapper around <see cref="HttpCookie"/> object that will be used for storing
    /// authentication information.
    /// </summary>
    public class AuthenticationCookie
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="AuthenticationCookie"/> class.
        /// </summary>
        /// <param name="userName">Name of the authenticated user.</param>
        /// <param name="userType">Type of the authenticated user.</param>
        public AuthenticationCookie(string userName, UserTypes userType)
        {
            UserName = userName;
            UserType = userType;
        }

        /// <summary>
        /// Gets or sets the name of the authenticated user.
        /// </summary>
        public string UserName { get; set; }

        /// <summary>
        /// Gets or sets the type of the authenticated user.
        /// </summary>
        public UserTypes UserType { get; set; }

        /// <summary>
        /// Gets or sets the name of the cookie.
        /// </summary>
        public string CookieName { get; set; }

        /// <summary>
        /// Gets or sets the expiration date of the cookie.
        /// </summary>
        public DateTime Expires { get; set; }

        /// <summary>
        /// Gets authentication cookie from the specified request by cookie name.
        /// </summary>
        /// <param name="request">The request to find cookie in.</param>
        /// <param name="cookieName">Name of the cookie to find.</param>
        /// <param name="userType">Type of the user that should be associated with this cookie.</param>
        /// <returns>Authentication cookie object if found; null otherwise.</returns>
        public static AuthenticationCookie GetFromRequest(
            HttpRequestBase request, string cookieName, UserTypes userType)
        {
            var cookie = request.Cookies[cookieName];
            if (null == cookie)
                return null;

            FormsAuthenticationTicket ticket;

            try
            {
                ticket = FormsAuthentication.Decrypt(cookie.Value);

                if (ticket.Expired)
                    return null;

                UserTypes storedType = (UserTypes)byte.Parse(ticket.UserData);
                if (storedType != userType)
                    return null;
            }
            catch (ArgumentException ex)
            {
                Trace.TraceWarning("Authentication cookie {0} could not be decrypted: {1}", cookieName, ex);
                return null;
            }
            catch (FormatException)
            {
                Trace.TraceWarning("Authentication cookie {0} contains invalid user type", cookieName);
                return null;
            }
            catch (HttpException ex)
            {
                Trace.TraceWarning("Authentication cookie {0} could not be decrypted: {1}", cookieName, ex);
                return null;
            }

            AuthenticationCookie retVal = new AuthenticationCookie(ticket.Name, userType);

            return retVal;
        }

        /// <summary>
        /// Appends authentication cookie to the specified response.
        /// </summary>
        /// <param name="response">The response to append cookie to.</param>
        /// <param name="cookieName">Name to use for the cookie.</param>
        /// <param name="persistent">Indicates whether persistent cookie should be created.</param>
        /// <exception cref="System.InvalidOperationException">
        /// Indicates that expiration date is not set.
        /// </exception>
        public void AppendToResponse(HttpResponseBase response, string cookieName, bool persistent = false)
        {
            if (Expires == DateTime.MinValue)
                throw new InvalidOperationException("Expiration date is not set");

            var ticket = new FormsAuthenticationTicket(
                2,
                UserName,
                DateTime.Now,
                Expires,
                persistent,
                ((byte)UserType).ToString(CultureInfo.InvariantCulture));

            var cookie = new HttpCookie(cookieName, FormsAuthentication.Encrypt(ticket));
            cookie.HttpOnly = true;
            if (persistent)
                cookie.Expires = Expires;
            response.Cookies.Add(cookie);
        }
    }
}